package se.webbzon.boltzmann.game.character;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.media.j3d.Transform3D;
import javax.vecmath.AxisAngle4d;

import se.webbzon.oschi01.worldobject.WorldMovableObject;
import se.webbzon.oschi01.worldobject.WorldObject;
import se.webbzon.oschi01.worldobject.WorldStaticObject;
import se.webbzon.oschi01.worldobject.appearance.Appearance3D;
import se.webbzon.oschi01.worldobject.appearance.SimpleGroupAppearance3D;
import se.webbzon.oschi01.worldobject.appearance.XYPlaneAppearance3D;

public abstract class AbstractCharacter extends WorldMovableObject {
	
	// A static map of all character texture sets loaded into this class
	private static final Map<String,AbstractCharacterTextureSet> textureSetMap = 
			new HashMap<String,AbstractCharacterTextureSet>();
	
	// A set of all texture sets associated with this character
	private AbstractCharacterTextureSet characterTextureSet;
	
	// The current action of the character
	private CharacterActionEnum action;
	
	// The current facing of the character
	private CharacterFacing facing;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new abstract game character from the static collection of 
	 * character texture sets loaded into this class. **/
	public AbstractCharacter(int registeredMethods, String characterTextureSetName) 
			throws CharacterException {
		this(registeredMethods,textureSetMap.get(characterTextureSetName));
	}

	/** Creates a new abstract game character with a given character texture set. **/
	public AbstractCharacter(int registeredMethods, AbstractCharacterTextureSet characterTextureSet) 
			throws CharacterException {
		super(	registeredMethods, 0, 
				createCharacter());
		if (	(registeredMethods & WorldObject.onPostDraw) > 0 ||
				(registeredMethods & WorldObject.onPreDraw) > 0)
			throw new CharacterException("Abstract characters are not allowed to draw on hud");
		if (characterTextureSet == null)
			throw new CharacterException("The given AbstractCharacterTextureSet cannot be null.");
		this.characterTextureSet = characterTextureSet;
		facing = CharacterFacing.RIGHT_DOWN;
		action = CharacterActionEnum.STAND;
		updateAppearance();
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns a static map where all character texture sets can be stored to use
	 * in the AbstractCharacter constructor. **/
	public static Map<String,AbstractCharacterTextureSet> getCharacterTextureSetMap() {
		return textureSetMap;
	}
	
	/** Returns true if a character texture set was found with the given name and
	 * the character texture set was set for the character. **/
	public boolean setCharacterTextureSet(String characterTextureSetName) {
		return setCharacterTextureSet(textureSetMap.get(characterTextureSetName));
	}
	
	/** Returns true if the character texture set was set to the given character texture set. **/
	public boolean setCharacterTextureSet(AbstractCharacterTextureSet characterTextureSet) {
		if (characterTextureSet != null) {
			this.characterTextureSet = characterTextureSet;
			updateAppearance();
			return true;
		}
		return false;
	}
	
	/** Returns true if the action of the character was set to the given action. **/
	public boolean setAction(CharacterActionEnum action) {
		final boolean hasAction = characterTextureSet.hasAction(action);
		if (hasAction && this.action != action) {
			this.action = action;
			updateAppearance();
		}
		return hasAction;
	}
	
	/** Returns the action of the character. **/
	public CharacterActionEnum getAction() {
		return action;
	}
	
	/** Sets the facing of the character. **/
	public void setFacing(CharacterFacing facing) {
		if (this.facing != facing) {
			this.facing = facing;
			updateAppearance();	
		}
	}
	
	/** Sets the facing from an angle between 0-2*pi. **/
	public void setFacing(double direction) {
		// Do not change facing during this conditions
		switch (facing) {
		case RIGHT_UP:
			if (direction > 11*Math.PI/18 && direction < 17*Math.PI/9)
				break;
			return;
		case LEFT_UP:
			if (direction < 7*Math.PI/18 || direction > 10*Math.PI/9)
				break;
			return;
		case LEFT_DOWN:
			if (direction < 8*Math.PI/9 || direction > 29*Math.PI/18)
				break;
			return;
		case RIGHT_DOWN:
			if (direction < 25*Math.PI/18 && direction > Math.PI/9)
				break;
			return;
		}
		// Change facing if the conditions are not fulfilled
		if (direction < Math.PI) {
			if (direction < Math.PI/2)
				setFacing(CharacterFacing.RIGHT_UP);
			else
				setFacing(CharacterFacing.LEFT_UP);
		} else {
			if (direction < 3*Math.PI/2)
				setFacing(CharacterFacing.LEFT_DOWN);
			else
				setFacing(CharacterFacing.RIGHT_DOWN);
		}
	}
	
	/** Sets the facing so this character faces another instance. **/
	public void setFacing(WorldStaticObject other) {
		final double direction = Math.atan2(getZ() - other.getZ(),
											other.getX() - getX());
		if (direction > 0)
			setFacing(direction);
		else
			setFacing(2 * Math.PI + direction);
	}
	
	/** Returns the facing of the character. **/
	public CharacterFacing getFacing() {
		return facing;
	}
	
	public void setActionSpeed(double speed) {
		// Retrieve the group
		SimpleGroupAppearance3D group = 
				(SimpleGroupAppearance3D) getAppearance();
				
		// Retrieve the front and back
		Iterator<Appearance3D> appearances = group.getAppearances().iterator();
		XYPlaneAppearance3D front = (XYPlaneAppearance3D) appearances.next();
		XYPlaneAppearance3D back = (XYPlaneAppearance3D) appearances.next();
		
		front.setTextureSpeed(speed);
		back.setTextureSpeed(speed);
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Updates the appearance of the character. **/
	private void updateAppearance() {
		// Retrieve the group
		SimpleGroupAppearance3D group = 
				(SimpleGroupAppearance3D) getAppearance();
		
		// Retrieve the front and back
		Iterator<Appearance3D> appearances = group.getAppearances().iterator();
		XYPlaneAppearance3D front = (XYPlaneAppearance3D) appearances.next();
		XYPlaneAppearance3D back = (XYPlaneAppearance3D) appearances.next();
		
		front.setTextureSpeed(characterTextureSet.getTextureSpeed(action));
		back.setTextureSpeed(characterTextureSet.getTextureSpeed(action));
		switch (facing) {
		case RIGHT_UP:
			front.setTextureSet(characterTextureSet.getRightUp(action));
			back.setTextureSet(characterTextureSet.getLeftDown(action));
			return;
		case LEFT_UP:
			front.setTextureSet(characterTextureSet.getLeftUp(action));
			back.setTextureSet(characterTextureSet.getRightDown(action));
			return;
		case LEFT_DOWN:
			front.setTextureSet(characterTextureSet.getLeftDown(action));
			back.setTextureSet(characterTextureSet.getRightUp(action));
			return;
		case RIGHT_DOWN:
			front.setTextureSet(characterTextureSet.getRightDown(action));
			back.setTextureSet(characterTextureSet.getLeftUp(action));
			return;
		}
		
	}
	
	/** Creates the appearance of this character. **/
	private static Appearance3D createCharacter() {
		XYPlaneAppearance3D 	front = new XYPlaneAppearance3D(-1,0,false),
								back = new XYPlaneAppearance3D(-1,0,false);
		
		// Flip the back-side of the plane
		final AxisAngle4d rotation = new AxisAngle4d(0,1,0,Math.PI);
		final Transform3D transform = new Transform3D();
		transform.setRotation(rotation);
		back.setTransform(transform);
		
		ArrayList<Appearance3D> appearances = 
				new ArrayList<Appearance3D>();
		appearances.add(front);
		appearances.add(back);
		return new SimpleGroupAppearance3D(appearances);
	}
	
}
